/*
 *  Instructions.h
 *  vorpal
 *
 *  Created by rwebb on 25/04/08.
 *
 */

#ifndef VORPAL_INSTRUCTIONS_H
#define VORPAL_INSTRUCTIONS_H

#include "Common.h"

namespace Vorpal{
	
/* 
	The Vorpal VM uses an unusual instruction format.
	Each Code block is array of Cells (see Common.h).
	The first group of cells contains byte codes (padded to a Cell multiple).
	The next group of cells contains Cell width operands.
	Each Jump lists an instruction offset and an operand offset in the short fields of its Operand.
	The first few cells contain unique information:
	[0] = size of code block
	[1] = numParameters
	[2] = indexOfVariableParameter (0 if none)
	[3] = numLocals
	[4] = index to Operands, k
	[5] = first instruction Cell
	...
	[k] = first operand Cell
	...
*/
	enum{
		kCodeSizeIndex,
		kCodeNumParamIndex,
		kCodeVarParamIndex,
		kCodeNumLocalIndex,
		kCodeFirstOperandIndexIndex,
		kCodeDebugInfoIndex,
		kCodeFirstInstructionIndex
	};

	class ExecutionInfo{
	public:
		ExecutionInfo(const Cell *startOfCode) : first(startOfCode){
			ip = (int8_t *)&first[kCodeFirstInstructionIndex];
			op = first + first[kCodeFirstOperandIndexIndex].as_uint;
			debug = 0;
			if(first[kCodeDebugInfoIndex].as_uint != 0){
				debug = first + first[kCodeDebugInfoIndex].as_uint;
			}
			numParams = first[kCodeNumParamIndex].as_uint;
			varParam = first[kCodeVarParamIndex].as_uint;
			numLocals = first[kCodeNumLocalIndex].as_uint;
			firstLocal = 0; // set by VM
		}
		
		static uint CodeSizeInCells(const Cell *code){
			return code[kCodeSizeIndex].as_uint;
		}
		
		const char *DebugSourceFile(){
			if(debug){
				return (const char *)(debug + 1);
			}
			return "unknown file";
		}
		
		uint DebugLineNumber(){
			if(debug){
				const Cell *debugLines = debug + debug[0].as_uint;
				uint debugSize = debugLines[0].as_uint;
				debugLines += 1;
				uint index = OffsetInCompiledCode();
				if(index >= debugSize){
					printf("debug index out of bounds: %u, %u\n", index, debugSize);
					index = debugSize - 1;
				}
				return debugLines[index].as_uint;
			}
			return 0;
		}
		
		void NextIp(){
			ip += 1;
		}
		
		void NextOp(){
			op += 1;
		}
		
		void OffsetOp(int delta){
			op += delta;
		}
		
		void Jump(int ipOffset, int opOffset){
			ip += ipOffset;
			op += opOffset;
		}
		
		int8_t FromIp() const{
			return *ip;
		}

		int8_t FromIp(int offset) const{
			return ip[offset];
		}
		
		Cell FromOp() const{
			return *op;
		}
		
		bool AtEnd(){
			return ip >= (int8_t *) (first + first[kCodeFirstOperandIndexIndex].as_uint);
		}
		
		const Cell *OperandPointer() const{ return op; }
		
		const int8_t *InstructionPointer() const{ return ip; }
		
		bool Same(const Cell *startOfCode) const{ return startOfCode == first; }
		uint NumParams() const{ return numParams; }
		uint VarParamIndex() const{ return varParam; }
		uint NumLocals() const{ return numLocals; }
		uint FirstArg() const{ return firstLocal - numParams; }
		uint FirstLocal() const{ return firstLocal; }
		void FirstLocal(uint newValue){ firstLocal = newValue; }
		
		uint OffsetInCompiledCode(){
			const int8_t *instruction = InstructionPointer();
			int8_t *codeStart = (int8_t *)&first[kCodeFirstInstructionIndex];
			Assert(instruction >= codeStart); // TODO: should also check upper bound
			return instruction - codeStart;
		}
		
		void Restart(){
			ip = (int8_t *)&first[kCodeFirstInstructionIndex];
			op = first + first[kCodeFirstOperandIndexIndex].as_uint;
		}
		
	private:
		const int8_t *ip;
		const Cell *op;
		const Cell *first;
		const Cell *debug;
		uint numParams;
		uint varParam;
		uint numLocals;
		uint firstLocal;
	};
		
	enum{
		// should never be executed, used only for cell padding
		NOP = 0, 	
		
		// Access locals and arguments		
		GET, 			
		SET,
		
		// Drop the top of the data stack
		DROP, 		
		
		// Get and set slots in an object
		OBJ_GET, 							
		OBJ_SET,							 

		// Execution control
		MESSAGE,							
		JUMP,										
		JUMP_F,							
		RETURN,											

		// Create objects
		NUM,								
		SMALL, // Tiny integers represented by signed byte									
		OBJ,								
		STR,								
		CODE,	
		
		// Get a object named by a hashed symbol
		SYMBOL,				
		
		NumOpcodes  // must remain last in enum list
	};

	typedef struct{
		int opcode; // allows us to check that they are in the right order in the enum
		const char *operandString;
		int smallOperands;
		int bigOperands;
		int  numArgs;
		int  numReturned;
		const char *name;
	} InstructionInfo;

	#define kTailCallMarker 1
	
	static const InstructionInfo instructions[NumOpcodes] = {
		//      opcode          operandString			smallOperands         bigOperands     numArgs         numReturned     name
		{       NOP,            "",						0,                    0,              0,              0,              "NOP"           },
		{       GET,            "index",                1,					  0,		      0,              1,              "GET"           },
		{       SET,            "index",                1,                    0,		      1,              0,              "SET"           },
		{       DROP,           "",                     0,                    0,              1,              0,              "DROP"          },
		{       OBJ_GET,        "hash|0",               0,                    1,              1,              1,              "OBJ_GET"       }, // may use 2 args
		{       OBJ_SET,        "hash|0",               0,                    1,              2,              1,              "OBJ_SET"       }, // may use 3 args
		{		MESSAGE,        "numArgs, hash|0",      1,                    1,             -1,              1,              "MESSAGE"       },
		{       JUMP,           "offset16,16",          0,                    1,              0,              0,              "JUMP"          },
		{       JUMP_F,         "offset16,16",          0,                    1,              0,              0,              "JUMP_F"        },
		{       RETURN,         "index|0",              1,                    0,              1,              1,              "RETURN"        },
		{       NUM,            "value",                0,                    1,              0,              1,              "NUM"           },
		{       SMALL,          "value",                1,                    0,              0,              1,              "SMALL"         },
		{       OBJ,            "numArgs",              1,                    0,              0,              1,              "OBJ"           },
		{       STR,            "size...",              0,                   -1,              0,              1,              "STR"           },
		{       CODE,           "size...",              0,                   -1,              0,              1,              "CODE"          },
		{       SYMBOL,         "hash",                 0,                    1,              0,              1,              "SYMBOL"        },
	};
	
	void AssertInstructionSanity();
}



#endif
